/*
 * Copyright (c) 2017, hehanpeng 何汉鹏 (hehanpeng@csii.com.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hehanpeng.springbootnetty.java8583.core;

import com.hehanpeng.springbootnetty.java8583.constant.Iso8583ConstantValue;
import com.hehanpeng.springbootnetty.java8583.field.Iso8583Field;
import com.hehanpeng.springbootnetty.java8583.field.Iso8583FieldType;
import com.hehanpeng.springbootnetty.java8583.field.Iso8583FillBlankStrategy;
import com.hehanpeng.springbootnetty.java8583.special.SpecialField;
import com.hehanpeng.springbootnetty.java8583.util.EncodeUtil;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.TreeMap;

/**
 * <p>可以表示一个ISO8583报文协议消息的对象</p>
 *
 * @author hehanpeng@csii.com.com
 * @ClassName: Iso8583Message
 * @Description: 可以表示一个ISO8583报文协议消息的对象
 * @date 2017年3月23日 下午6:13:30
 */
public class Iso8583CibMessage {

    /**
     * <p>自身所拥有的一个报文格式工厂</p>
     */
    private Iso8583MessageCibFactory factory = null;
    /**
     * <p>当前报文所对应的一个bitmap 64/128 域规范由本身持有的factory.isBit128()方法决定</p>
     */
    private byte[] bitmap = null;
    private Map<Integer, Iso8583Field> values = new TreeMap<Integer, Iso8583Field>();
    private String mti = "";

    /**
     * <p>构造函数，需要一个Iso8583MessageFactory来约束报文解析规范</p>
     */
    public Iso8583CibMessage(Iso8583MessageCibFactory factory) {
        if (null == factory)
            throw new NullPointerException("required param factory is null");
        this.factory = factory;
        if (factory.isBit128()) {
            bitmap = new byte[128];
            bitmap[0] = 1;
        } else {
            bitmap = new byte[64];
            bitmap[0] = 0;
        }
    }

    /**
     * <p>向报文中插入值，返回自身对象</p>
     * <p>当出现以下情况时，会忽略操作，直接返回，而不会抛出异常</p>
     * <ol>
     * <li>字段索引小于2时；</li>
     * <li>使用64域规范时，字段索引大于64；</li>
     * <li>使用128域规范时，字段索引大于128；</li>
     * </ol>
     *
     * @param index 域字段索引
     * @param value 域字段值
     * @return Iso8583Message
     * @Title: setValue
     * @Description: 向报文中插入值
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午10:37:48
     */
    public Iso8583CibMessage setValue(int index, String value) {
        if (index < 2)
            return this;
        if (true == factory.isBit128() && index > 128)
            return this;
        if (false == factory.isBit128() && index > 64)
            return this;
        Iso8583FieldType type = factory.getFieldType(index);
        //将数据填入map，已处理填充位数据
        values.put(index, new Iso8583Field(index, getFieldValue(value, type, true), type, type.getAlias()));
        //位图标记
        bitmap[index - 1] = 1;
        return this;
    }

    /**
     * <p>获取报文中的某个域的值</p>
     * <p>不关注填充内容，获取到的结果值中不包含填充内容</p>
     *
     * @param index 域的索引值
     * @return Iso8583IsoField
     * @Title: getValue
     * @Description: 获取报文中的某个域的值
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午10:50:59
     */
    public Iso8583Field getValue(int index) {
        return values.get(index);
    }

    /**
     * <p>获取报文中的某个域的值</p>
     * <p>关注填充内容，获取到的值中包含填充值</p>
     *
     * @param index       域的索引值
     * @param filledValue 是否要包含填充位数据
     * @return Iso8583Field
     * @Title: getValue
     * @Description: 获取报文中的某个域的值
     * @author hehanpeng@csii.com.com
     * @date 2017年3月27日 下午12:33:53
     */
    public Iso8583Field getValue(int index, boolean filledValue, String alias) {
        Iso8583Field field = getValue(index);
        Iso8583FieldType type = factory.getFieldType(index);
        String value = getFieldValue(field.getValue(), type, filledValue);
        return new Iso8583Field(index, value, type, alias);
    }

    /**
     * <p>返回bitmap的位图内容</p>
     * <p>返回值示例：0110000000111100000000001000000100001010110100001000110000010001</p>
     *
     * @Title: getBitmapBitString
     * @Description: 返回bitmap的位图内容
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:08:16
     */
    public String getBitmapBitString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bitmap.length; i++) {
            sb.append(bitmap[i]);
        }
        return sb.toString();
    }

    /**
     * <p>获取bitmap的字节数组内容</p>
     *
     * @Title: getBitmapBytes
     * @Description: 获取bitmap的字节数组内容
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:10:00
     */
    public byte[] getBitmapBytes() {
        return EncodeUtil.binary(getBitmapBitString());
    }

    /**
     * <p>获取bitmap的字节数组内容</p>
     *
     * @Title: getBitmapBytes
     * @Description: 获取bitmap的字节数组内容
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:10:00
     */
    public byte[] writeBitmapBytes() {
        return EncodeUtil.binary2(getBitmapBitString());
    }

    /**
     * <p>获取bitmap字节数组的字符串表现形式</p>
     * <p>返回示例：603C00810AD08C11</p>
     *
     * @Title: getBitmapString
     * @Description: 获取bitmap字节数组的字符串表现形式
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:12:15
     */
    public String getBitmapString() {
        return EncodeUtil.hex(getBitmapBytes());
    }

    /**
     * <p>格式化消息输出</p>
     * <p>建议用于开发阶段调试，因为其打印内容未做掩码，为纯明文内容，不安全</p>
     *
     * @return String 格式化输出
     * @Title: toFormatString
     * @Description: 格式化消息输出
     * @author hehanpeng@csii.com.com
     * @date 2017年7月25日 下午3:08:44
     */
    public String toFormatString() {
        StringBuilder sb = new StringBuilder();
        String format = "%s\t:[%s]";
        sb.append("\n");
        sb.append(String.format(format, Iso8583ConstantValue.MTI.getValue(), getMti()));
        sb.append("\n");
        sb.append(String.format(format, Iso8583ConstantValue.BITMAP.getValue(), getBitmapString()));
        sb.append("\n");
        for (Map.Entry<Integer, Iso8583Field> entry : values.entrySet()) {
            Iso8583Field field = entry.getValue();
            sb.append(String.format(format, field.getIndex(), field.getValue()));
            sb.append("\n");
        }
        return sb.toString();
    }

    /**
     * <p>解析报文协议，返回字节数组，用于应用间的消息的传输</p>
     *
     * @return byte[] 用于传输的报文包
     * @Title: getBytes
     * @Description: 解析报文协议，返回字节数组，用于应用间的消息的传输
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:13:39
     */
    public byte[] getBytes() {
        //结果返回数据内容
        byte[] resultContent = new byte[0];
        try {
            //创建一个不用关闭的输出流，用于最后的byte[]转换，不用维护数组的pos
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            //写入mti+bitmap+8583报文数据（设置有校验位，则包含，没设置校验位，则不包含）
            baos.write(getMacBlock());
            //写入校验域
            baos.write(getMac());
            //计算报文整体长度
            String size = "00" + baos.size();
//            size = size.substring(size.length() - factory.getMsgLength() * 2);
            //准备最终报文数据
            ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
            //写入报文长度
            resultStream.write(size.getBytes("UTF-8"));
            //写入报文信息
            baos.writeTo(resultStream);
            resultContent = resultStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return resultContent;
    }

    /**
     * 获取当前报文的完整字符串表示形式
     *
     * @return String 完整字符串表示形式
     * @Title: getBytesString
     * @Description: 获取当前报文的完整字符串表示形式
     * @author hehanpeng@csii.com.com
     * @date 2017年8月3日 下午7:52:01
     */
    public String getBytesString() {
        String content = null;
        try {
            content = new String(getBytes(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return content;
    }

    /**
     * <p>获取用于计算mac的macBlock的字符串表示</p>
     * <p>macBlock : mti+bitmap+data(除去校验位的8583报文数据)</p>
     *
     * @return String macBlock的字符串格式数据
     * @Title: getMacBlockString
     * @Description: 获取用于计算mac的macBlock的字符串表示
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:35:49
     */
    public String getMacBlockString() {
        return EncodeUtil.hex(getMacBlock());
    }

    /**
     * <p>获取用于计算mac的macBlock的字节数组表示</p>
     * <p>macBlock : mti+bitmap+data(出去校验位的8583报文数据)</p>
     *
     * @return byte[] macBlock的字节数组格式数据
     * @Title: getMacBlock
     * @Description: 获取用于计算mac的macBlock的字节数组表示
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:37:11
     */
    public byte[] getMacBlock() {
        byte[] resultContent = new byte[0];
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            //写入mit
            baos.write(getMtiBytes());
            //写入bitmap
            baos.write(writeBitmapBytes());
            //循环写入字段信息
            for (Map.Entry<Integer, Iso8583Field> entry : values.entrySet()) {
                //不写入校验域
                if (entry.getKey() != bitmap.length) {
                    Iso8583Field field = entry.getValue();
                    baos.write(parse(field.getValue(), field.getFieldType()));
                }
            }
            resultContent = baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return resultContent;
    }

    /**
     * <p>获取校验域的字符串形式值</p>
     *
     * @Title: getMacString
     * @Description: 获取校验域的字符串形式值
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:48:26
     */
    public String getMacString() {
        return EncodeUtil.hex(getMac());
    }

    /**
     * <p>获取校验域的字节数组格式</p>
     *
     * @Title: getMac
     * @Description: 获取校验域的字节数组格式
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:52:26
     */
    public byte[] getMac() {
        byte[] resultContent = new byte[0];
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            //取最后一个域
            Iso8583Field field = values.get(bitmap.length);
            if (null != field) {
                baos.write(parse(field.getValue(), field.getFieldType()));
                resultContent = baos.toByteArray();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return resultContent;
    }

    /**
     * <p>获取mti的字节数组格式</p>
     *
     * @Title: getMtiBytes
     * @Description: 获取mti的字节数组格式
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:57:10
     */
    public byte[] getMtiBytes() {
        Iso8583FieldType type = factory.getMtiType();
        byte[] content = parse(this.mti, type);
        return content;
    }

    /**
     * <p>解析字段内容</p>
     *
     * @param value 当前字段的值
     * @param type  当前字段的类型
     * @Title: parse
     * @Description: 解析字段内容
     * @author hehanpeng@csii.com.com
     * @date 2017年3月24日 上午11:57:35
     */
    private byte[] parse(String value, Iso8583FieldType type) {
        byte[] result = new byte[0];
        if (null != value) {
            //取当前字段对应的特殊处理器
            SpecialField fieldHandle = this.factory.getSpecialFieldHandler(type.getFieldIndex());
            if (null != fieldHandle) {
                Iso8583FieldType newType = new Iso8583FieldType(fieldHandle.forGetBytes(type, getMti()), type.getFieldLength());
                newType.setFieldIndex(type.getFieldIndex());
                newType.setFillBlankStrategy(type.getFillBlankStrategy());
//				//使用例外的数据类型
                type = newType;
            }
            //原执行逻辑
            switch (type.getFieldTypeValue()) {
                case CHAR: {
                    if (value.length() > type.getFieldLength()) {
                        value = value.substring(0, type.getFieldLength());
                    }
                    result = value.getBytes(this.factory.getCharset());
                    break;
                }
                case LLVAR_CHAR: {
                    result = variableLengthParse(value, 2, true);
                    break;
                }
                case LLLVAR_CHAR: {
                    result = variableLengthParse(value, 3, true);
                    break;
                }
                default: {
                    break;
                }
            }
        }
        return result;
    }

    private byte[] variableLengthParse(String value, int dataLength, boolean isByteLength) {
        String fieldLength = null;
        try {
            fieldLength = String.format("%0" + dataLength + "d", value.getBytes("UTF-8").length);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        byte[] content = null;
        try {
            content = (fieldLength + value).getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return content;
    }

    /**
     * 获取某一个字段值的填充/不填充结果
     *
     * @param value       原报文内容
     * @param type        当前字段对应的字段类型
     * @param filledValue 读取填充值：true，读取有填充的内容，false，读取没有填充的值
     * @return String 报文对应的内容
     * @Title: getField
     * @Description: 获取某一个字段值的填充/不填充结果
     * @author hehanpeng@csii.com.com
     * @date 2017年3月27日 上午11:59:02
     */
    private String getFieldValue(String value, Iso8583FieldType type, boolean filledValue) {
        if (Iso8583FieldType.FieldTypeValue.CHAR == type.getFieldTypeValue()) {
            Iso8583FillBlankStrategy fillBlankStrategy = type.getFillBlankStrategy();
            //用于填充的填充字符
            String fillChar = String.valueOf(fillBlankStrategy.getValue());
            try {
                //获取补齐结果与非补齐结果报文内容
                if (fillBlankStrategy.isLeftAppend()) {
                    while (value.getBytes("UTF-8").length < type.getFieldLength()) {
                        value = fillChar + value;
                    }
                } else {
                    while (value.getBytes("UTF-8").length < type.getFieldLength()) {
                        value = value + fillChar;
                    }
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return value;
        } else {
            return value;
        }
    }

    // >> setter & getter

    public String getMti() {
        return mti;
    }

    /**
     * <p>setter</p>
     */
    public Iso8583CibMessage setMti(String mti) {
        this.mti = mti;
        return this;
    }

    /**
     * <p>getter</p>
     */
    public Map<Integer, Iso8583Field> getAllValues() {
        return this.values;
    }

    /**
     * 比较两个Iso8583Message对象是否一样
     *
     * @param message 目标对象
     * @return boolean 比较结果
     * @Title: compareWith
     * @Description: 比较两个Iso8583Message对象是否一样
     * @author hehanpeng@csii.com.com
     * @date 2017年8月3日 下午7:58:54
     */
    public boolean compareWith(Iso8583CibMessage message) {
        if (null == message) {
            return false;
        }
        if (this == message) {
            return true;
        }
        return this.getBytesString().equals(message.getBytesString());
    }

}
